// SPDX-FileCopyrightText: Adam Evyčędo
//
// SPDX-License-Identifier: GPL-3.0-or-later

package xyz.apiote.bimba.czwek.departures

import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.content.res.Configuration.*
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.Marker
import org.osmdroid.views.overlay.TilesOverlay
import org.osmdroid.views.overlay.gestures.RotationGestureOverlay
import xyz.apiote.bimba.czwek.R
import xyz.apiote.bimba.czwek.dpToPixelI
import xyz.apiote.bimba.czwek.repo.Departure
import xyz.apiote.bimba.czwek.repo.Vehicle
import java.time.ZoneId
import java.time.ZonedDateTime
import java.util.*


class BimbaDepartureViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
	val root: View = itemView.findViewById(R.id.departure)
	val lineIcon: ImageView = itemView.findViewById(R.id.line_icon)
	val departureTime: TextView = itemView.findViewById(R.id.departure_time)
	val lineName: TextView = itemView.findViewById(R.id.departure_line)
	val headsign: TextView = itemView.findViewById(R.id.departure_headsign)

	companion object {
		fun bind(
			departure: Departure,
			holder: BimbaDepartureViewHolder?,
			context: Context?,
			onClickListener: (Departure) -> Unit
		) {
			holder?.root?.setOnClickListener {
				onClickListener(departure)
			}
			holder?.lineIcon?.setImageDrawable(departure.vehicle.Line.icon(context!!))
			holder?.lineIcon?.contentDescription = departure.vehicle.Line.kind.name
			holder?.lineName?.text = departure.vehicle.Line.name
			holder?.headsign?.text =
				context?.getString(R.string.departure_headsign, departure.vehicle.Headsign)
			holder?.headsign?.contentDescription =
				context?.getString(
					R.string.departure_headsign_content_description,
					departure.vehicle.Headsign
				)

			holder?.departureTime?.text = departure.statusText(context)
		}
	}
}

class BimbaDeparturesAdapter(
	private val inflater: LayoutInflater,
	private val context: Context?,
	private var departures: List<Departure>,
	private val onClickListener: ((Departure) -> Unit)
) :
	RecyclerView.Adapter<BimbaDepartureViewHolder>() {
	private var lastUpdate: ZonedDateTime =
		ZonedDateTime.of(0, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault())

	inner class DiffUtilCallback(
		private val oldDepartures: List<Departure>,
		private val newDepartures: List<Departure>
	) : DiffUtil.Callback() {
		override fun getOldListSize() = oldDepartures.size

		override fun getNewListSize() = newDepartures.size

		override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
			oldDepartures[oldItemPosition].ID == newDepartures[newItemPosition].ID

		override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
			val oldDeparture = oldDepartures[oldItemPosition]
			val newDeparture = newDepartures[newItemPosition]
			return oldDeparture.vehicle.Line == newDeparture.vehicle.Line && oldDeparture.vehicle.Headsign == newDeparture.vehicle.Headsign &&
				oldDeparture.statusText(context) == newDeparture.statusText(context)
		}
	}

	private var departuresPositions: MutableMap<String, Int> = HashMap()

	init {
		departures.forEachIndexed { i, departure ->
			departuresPositions[departure.ID] = i
		}
	}

	override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BimbaDepartureViewHolder {
		val rowView = inflater.inflate(R.layout.departure, parent, false)
		return BimbaDepartureViewHolder(rowView)
	}

	override fun onBindViewHolder(holder: BimbaDepartureViewHolder, position: Int) {
		BimbaDepartureViewHolder.bind(departures[position], holder, context, onClickListener)
	}

	override fun getItemCount(): Int = departures.size

	fun get(ID: String): Departure? {
		val position = departuresPositions[ID]
		return if (position == null) {
			null
		} else {
			departures[position]
		}
	}

	fun update(departures: List<Departure>) {
		val newPositions: MutableMap<String, Int> = HashMap()
		departures.forEachIndexed { i, departure ->
			newPositions[departure.ID] = i
		}
		val diff = DiffUtil.calculateDiff(DiffUtilCallback(this.departures, departures))

		this.departures = departures
		departuresPositions = newPositions
		lastUpdate = ZonedDateTime.now()
		diff.dispatchUpdatesTo(this)
	}

	fun refreshItems() {
		update(this.departures)
	}
}

class DepartureBottomSheet(private var departure: Departure) : BottomSheetDialogFragment() {
	companion object {
		const val TAG = "DepartureBottomSheet"
	}

	private var cancelCallback: (() -> Unit)? = null

	fun setOnCancel(callback: () -> Unit) {
		cancelCallback = callback
	}

	override fun onCancel(dialog: DialogInterface) {
		super.onCancel(dialog)
		cancelCallback?.let { it() }
	}

	fun departureID(): String {
		return departure.ID
	}

	fun update(departure: Departure) {
		this.departure = departure
		this.view?.let { context?.let { ctx -> setContent(it, ctx, true) } }
	}

	private fun setContent(view: View, ctx: Context, updating: Boolean = false) {
		view.apply {
			findViewById<TextView>(R.id.time).text = departure.timeString(ctx)

			findViewById<ImageView>(R.id.rt_icon).apply {
				visibility = if (departure.isRealtime) {
					View.VISIBLE
				} else {
					View.GONE
				}
			}
			findViewById<ImageView>(R.id.wheelchair_icon).apply {
				visibility = if (departure.vehicle.let {
						it.getCapability(Vehicle.Capability.LOW_FLOOR) || it.getCapability(Vehicle.Capability.LOW_ENTRY) || it.getCapability(
							Vehicle.Capability.RAMP
						)
					}) {
					View.VISIBLE
				} else {
					View.GONE
				}
			}

			findViewById<TextView>(R.id.line).apply {
				contentDescription = getString(
					R.string.vehicle_headsign_content_description,
					departure.vehicle.Line.name,
					departure.vehicle.Headsign
				)
				text = getString(
					R.string.vehicle_headsign,
					departure.vehicle.Line.name,
					departure.vehicle.Headsign
				)
			}

			findViewById<TextView>(R.id.boarding_text).text = departure.boardingText(ctx)
			// todo units -- [3.2] settings or system-based
			findViewById<TextView>(R.id.speed_text).text =
				getString(R.string.speed_in_km_per_h, departure.vehicle.Speed * 3.6)
			findViewById<TextView>(R.id.congestion_text).text = departure.vehicle.congestion(ctx)
			findViewById<TextView>(R.id.occupancy_text).text = departure.vehicle.occupancy(ctx)

			findViewById<ImageView>(R.id.ac).visibility =
				if (departure.vehicle.getCapability(Vehicle.Capability.AC)) {
					View.VISIBLE
				} else {
					View.GONE
				}
			findViewById<ImageView>(R.id.bike).visibility =
				if (departure.vehicle.getCapability(Vehicle.Capability.BIKE)) {
					View.VISIBLE
				} else {
					View.GONE
				}
			findViewById<ImageView>(R.id.voice).visibility =
				if (departure.vehicle.getCapability(Vehicle.Capability.VOICE)) {
					View.VISIBLE
				} else {
					View.GONE
				}
			findViewById<ImageView>(R.id.ticket).visibility =
				if (departure.vehicle.let {
						it.getCapability(Vehicle.Capability.TICKET_DRIVER) || it.getCapability(Vehicle.Capability.TICKET_MACHINE)
					}) {
					View.VISIBLE
				} else {
					View.GONE
				}
			findViewById<ImageView>(R.id.usb).visibility =
				if (departure.vehicle.getCapability(Vehicle.Capability.USB_CHARGING)) {
					View.VISIBLE
				} else {
					View.GONE
				}
			findViewById<MapView>(R.id.map).let { map ->
				if (departure.vehicle.Position.isZero()) {
					map.visibility = View.GONE
					return@let
				}
				map.controller.apply {
					GeoPoint(
						departure.vehicle.location().latitude,
						departure.vehicle.location().longitude
					).let {
						if (updating) {
							animateTo(
								it, 19.0f.toDouble(), 3 * 1000
							)
						} else {
							setCenter(it)
							setZoom(19f.toDouble())
						}
					}
				}

				map.overlays.removeAll { marker ->
					marker is Marker
				}
				val marker = Marker(map).apply {
					position =
						GeoPoint(
							departure.vehicle.location().latitude,
							departure.vehicle.location().longitude
						)
					setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
					icon = context?.let { ctx -> departure.vehicle.icon(ctx, 2f) }
					setOnClickListener {}
				}
				map.overlays.add(marker)
				map.invalidate()
			}
		}
	}

	@SuppressLint("ClickableViewAccessibility")
	override fun onCreateView(
		inflater: LayoutInflater,
		container: ViewGroup?,
		savedInstanceState: Bundle?
	): View {
		val content = inflater.inflate(R.layout.departure_bottom_sheet, container, false)

		context?.let { ctx ->
			content.apply {
				findViewById<MapView>(R.id.map).let { map ->
					map.setTileSource(TileSourceFactory.MAPNIK)
					if (((context?.resources?.configuration?.uiMode ?: UI_MODE_NIGHT_UNDEFINED)
							and UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES
					) {
						map.overlayManager.tilesOverlay.setColorFilter(TilesOverlay.INVERT_COLORS)
					}
					map.zoomController.setVisibility(CustomZoomButtonsController.Visibility.NEVER)
					map.setOnTouchListener { _, _ -> true }
					map.setMultiTouchControls(true)
					map.overlays.add(RotationGestureOverlay(map).apply { isEnabled = true })
				}

				setContent(this, ctx)

				(dialog as BottomSheetDialog).behavior.peekHeight = dpToPixelI(180f)

			}
		}
		return content
	}
}